package com.timk.goserver.client.ui.screens;	// NOPMD - too many imports

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Random;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.KeyboardListenerAdapter;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.timk.goserver.client.model.ClientGameInfo;
import com.timk.goserver.client.model.GameInvite;
import com.timk.goserver.client.services.MessageService;
import com.timk.goserver.client.services.NoSessionException;
import com.timk.goserver.client.ui.widgets.CenteredDialog;
import com.timk.goserver.client.ui.widgets.ChooseUserText;
import com.timk.goserver.client.ui.widgets.FocusCommand;
import com.timk.goserver.client.ui.widgets.NoFocusListener;
import com.timk.goserver.client.ui.widgets.StatusLabel;

/**
 * Dialog for game invites
 * @author TKington
 *
 */
public class InviteDialog extends CenteredDialog {	// NOPMD - Complexity
	private static final int TYPE_NORMAL = 0;
	private static final int TYPE_RENGO = 1;
	
	final StatusPanel statusPanel;	// NOPMD - Explicit scoping
	
	private final ListBox typeList;
	private final ChooseUserText partnerText;
	private final HorizontalPanel partnerPanel;
	private final ChooseUserText oppText;
	private final ChooseUserText oppPartnerText;
	private final HorizontalPanel oppPartnerPanel;
	private final ListBox colorList;
	private final TextBox sizeText;
	private final CheckBox properCheck;
	private final TextBox handicapText;
	private final TextBox komiText;
	private final CheckBox ratedCheck;
	private final TextArea commentArea;
	private final Button sendButton;
	private final Button cancelButton;
	private final Button acceptButton;
	private final Button refuseButton;
	final StatusLabel status;	// NOPMD - Explicit scoping
	
	private final GameInvite invite;
	
	/**
	 * Creates an InviteDialog
	 * @param statusPanel StatusPanel, required if this is not a new invite
	 * @param invite the GameInvite
	 * @param newInvite true if this is a new invite
	 */
	public InviteDialog(StatusPanel statusPanel,	// NOPMD - Complexity
			GameInvite invite, boolean newInvite) {
		super(null);
		
		this.statusPanel = statusPanel;
		this.invite = invite;
		
		setText("Game Settings");
		
		VerticalPanel mainPanel = new VerticalPanel();
		mainPanel.setSpacing(2);
		
		HorizontalPanel typePanel = new HorizontalPanel();
		mainPanel.add(typePanel);
		Label typeLabel = new Label("Game Type:");
		typePanel.add(typeLabel);
		typeList = new ListBox();
		typeList.setVisibleItemCount(1);
		typeList.addItem("Normal");
		typeList.addItem("Rengo");
		typePanel.add(typeList);
		typePanel.setCellVerticalAlignment(typeLabel, 
				HasVerticalAlignment.ALIGN_MIDDLE);
		typePanel.setWidth("100%");
		typeLabel.setWidth("100%");
		typeList.setWidth("100%");
		typePanel.setCellWidth(typeLabel, "50%");
		typePanel.setCellWidth(typeList, "50%");
		
		partnerPanel = new HorizontalPanel();
		mainPanel.add(partnerPanel);
		HTML partnerLabel = new HTML("Partner:&nbsp;");
		partnerPanel.add(partnerLabel);
		partnerText = new ChooseUserText();
		partnerPanel.add(partnerText);
		partnerPanel.setCellVerticalAlignment(partnerLabel, 
				HasVerticalAlignment.ALIGN_MIDDLE);
		partnerPanel.setWidth("100%");
		partnerLabel.setWidth("100%");
		partnerText.setWidth("100%");
		partnerPanel.setCellWidth(partnerLabel, "50%");
		partnerPanel.setCellWidth(partnerText, "50%");
		
		HorizontalPanel oppPanel = new HorizontalPanel();
		mainPanel.add(oppPanel);
		HTML oppLabel = new HTML("Opponent:&nbsp;");
		oppPanel.add(oppLabel);
		oppText = new ChooseUserText();
		oppPanel.add(oppText);
		oppPanel.setCellVerticalAlignment(oppLabel, 
				HasVerticalAlignment.ALIGN_MIDDLE);
		oppPanel.setWidth("100%");
		oppLabel.setWidth("100%");
		oppText.setWidth("100%");
		oppPanel.setCellWidth(oppLabel, "50%");
		oppPanel.setCellWidth(oppText, "50%");
		
		oppPartnerPanel = new HorizontalPanel();
		mainPanel.add(oppPartnerPanel);
		HTML oppPartnerLabel = new HTML("Opponent:&nbsp;");
		oppPartnerPanel.add(oppPartnerLabel);
		oppPartnerText = new ChooseUserText();
		oppPartnerPanel.add(oppPartnerText);
		oppPartnerPanel.setCellVerticalAlignment(oppPartnerLabel, 
				HasVerticalAlignment.ALIGN_MIDDLE);
		oppPartnerPanel.setWidth("100%");
		oppPartnerLabel.setWidth("100%");
		oppPartnerText.setWidth("100%");
		oppPartnerPanel.setCellWidth(oppPartnerLabel, "50%");
		oppPartnerPanel.setCellWidth(oppPartnerText, "50%");
		
		HorizontalPanel sizePanel = new HorizontalPanel();
		mainPanel.add(sizePanel);
		Label sizeLabel = new Label("Board size:");
		sizePanel.add(sizeLabel);
		sizeText = new TextBox();
		sizePanel.add(sizeText);
		sizePanel.setCellVerticalAlignment(sizeLabel, 
				HasVerticalAlignment.ALIGN_MIDDLE);
		sizePanel.setWidth("100%");
		sizeLabel.setWidth("100%");
		sizeText.setWidth("100%");
		sizePanel.setCellWidth(sizeLabel, "50%");
		sizePanel.setCellWidth(sizeText, "50%");
		
		HorizontalPanel properPanel = new HorizontalPanel();
		mainPanel.add(properPanel);
		Label properLabel = new Label("Proper Handicap");
		properPanel.add(properLabel);
		properCheck = new CheckBox();
		properPanel.add(properCheck);
		properPanel.setCellVerticalAlignment(properLabel, 
				HasVerticalAlignment.ALIGN_MIDDLE);
		properPanel.setWidth("100%");
		properLabel.setWidth("100%");
		properCheck.setWidth("100%");
		properPanel.setCellWidth(properLabel, "50%");
		properPanel.setCellWidth(properCheck, "50%");
		
		HorizontalPanel colorPanel = new HorizontalPanel();
		mainPanel.add(colorPanel);
		Label colorLabel = new Label("My Color:");
		colorPanel.add(colorLabel);
		colorList = new ListBox();
		colorList.setVisibleItemCount(1);
		colorList.addItem("Nigiri");
		colorList.addItem("Black");
		colorList.addItem("White");
		colorPanel.add(colorList);
		colorPanel.setCellVerticalAlignment(colorLabel, 
				HasVerticalAlignment.ALIGN_MIDDLE);
		colorPanel.setWidth("100%");
		colorLabel.setWidth("100%");
		colorList.setWidth("100%");
		colorPanel.setCellWidth(colorLabel, "50%");
		colorPanel.setCellWidth(colorList, "50%");
		
		HorizontalPanel handicapPanel = new HorizontalPanel();
		mainPanel.add(handicapPanel);
		Label handicapLabel = new Label("Handicap:");
		handicapPanel.add(handicapLabel);
		handicapText = new TextBox();
		handicapPanel.add(handicapText);
		handicapPanel.setCellVerticalAlignment(handicapLabel, 
				HasVerticalAlignment.ALIGN_MIDDLE);
		handicapPanel.setWidth("100%");
		handicapLabel.setWidth("100%");
		handicapText.setWidth("100%");
		handicapPanel.setCellWidth(handicapLabel, "50%");
		handicapPanel.setCellWidth(handicapText, "50%");
		
		HorizontalPanel komiPanel = new HorizontalPanel();
		mainPanel.add(komiPanel);
		Label komiLabel = new Label("Komi:");
		komiPanel.add(komiLabel);
		komiText = new TextBox();
		komiPanel.add(komiText);
		komiPanel.setCellVerticalAlignment(komiLabel, 
				HasVerticalAlignment.ALIGN_MIDDLE);
		komiPanel.setWidth("100%");
		komiLabel.setWidth("100%");
		komiText.setWidth("100%");
		komiPanel.setCellWidth(komiLabel, "50%");
		komiPanel.setCellWidth(komiText, "50%");
		
		HorizontalPanel ratedPanel = new HorizontalPanel();
		mainPanel.add(ratedPanel);
		Label ratedLabel = new Label("Rated Game:");
		ratedPanel.add(ratedLabel);
		ratedCheck = new CheckBox();
		ratedPanel.add(ratedCheck);
		ratedPanel.setCellVerticalAlignment(ratedLabel, 
				HasVerticalAlignment.ALIGN_MIDDLE);
		ratedPanel.setWidth("100%");
		ratedLabel.setWidth("100%");
		ratedCheck.setWidth("100%");
		ratedPanel.setCellWidth(ratedLabel, "50%");
		ratedPanel.setCellWidth(ratedCheck, "50%");
		
		Label commentLabel = new Label("Comment:");
		mainPanel.add(commentLabel);
		commentArea = new TextArea();
		mainPanel.add(commentArea);
		commentArea.setWidth("100%");
		mainPanel.setCellWidth(commentArea, "200px");
		
		status = new StatusLabel();
		mainPanel.add(status);
		
		HorizontalPanel buttonPanel = new HorizontalPanel();
		mainPanel.add(buttonPanel);
		mainPanel.setCellHorizontalAlignment(buttonPanel,
				HasHorizontalAlignment.ALIGN_CENTER);
		
		if(newInvite) {
			sendButton = new Button("Send");
			sendButton.addClickListener(new ClickListener() {
				public void onClick(Widget sender) {
					sendInvite();
				}
			});
			sendButton.setWidth("60px");
			buttonPanel.add(sendButton);
			
			acceptButton = null;
			refuseButton = null;
		} else {
			acceptButton = new Button("Accept");
			acceptButton.addClickListener(new ClickListener() {
				public void onClick(Widget sender) {
					acceptInvite();
				}
			});
			acceptButton.setWidth("60px");
			buttonPanel.add(acceptButton);
			
			refuseButton = new Button("Refuse");
			refuseButton.addClickListener(new ClickListener() {
				public void onClick(Widget sender) {
					refuseInvite();
				}
			});
			refuseButton.setWidth("60px");
			buttonPanel.add(refuseButton);
			
			sendButton = null;
		}
		
		cancelButton = new Button("Cancel");
		cancelButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				hide();
			}
		});
		cancelButton.setWidth("60px");
		buttonPanel.add(cancelButton);
		
		properCheck.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				properChanged();
			}
		});
		
		typeList.addChangeListener(new ChangeListener() {
			public void onChange(Widget sender) {
				typeChanged();
			}
		});
		
		handicapText.addKeyboardListener(new KeyboardListenerAdapter() {
			public void onKeyUp(Widget sender, char keyCode, int modifiers) {
				handicapChanged();
			}
		});
		
		handicapText.setEnabled(false);
		komiText.setEnabled(false);
		colorList.setEnabled(false);
		
		setWidget(mainPanel);
		
		loadInvite(newInvite);
		
		if(newInvite) {
			int numPlayers = invite.getPlayerNames().size();
			if(numPlayers < 2) {
				TextBox userText = oppText.getTextBox();
				DeferredCommand.add(new FocusCommand(userText));
			} else {
				DeferredCommand.add(new FocusCommand(commentArea));
			}
		} else {
			typeList.setEnabled(false);
			partnerText.setEnabled(false);
			oppText.setEnabled(false);
			oppPartnerText.setEnabled(false);
			colorList.setEnabled(false);
			sizeText.setEnabled(false);
			properCheck.setEnabled(false);
			handicapText.setEnabled(false);
			komiText.setEnabled(false);
			ratedCheck.setEnabled(false);
			
			commentArea.addFocusListener(
					new NoFocusListener(commentArea));
			
			if(invite.isProperHandicap()) {
				colorPanel.setVisible(false);
				handicapPanel.setVisible(false);
				komiPanel.setVisible(false);
			}
		}
	}
	
	public boolean onKeyDownPreview(char key, int modifiers) {
		if(key == KeyboardListener.KEY_ESCAPE) {
			hide();
		} else if(key == KeyboardListener.KEY_ENTER && sendButton != null) {
			sendInvite();
		}
		return super.onKeyDownPreview(key, modifiers);
	}
	
	final void properChanged() {
		boolean proper = properCheck.isChecked();
		handicapText.setEnabled(!proper);
		komiText.setEnabled(!proper);
		colorList.setEnabled(!proper);
	}
	
	final void typeChanged() {
		switch(typeList.getSelectedIndex()) {
		case TYPE_NORMAL:
			partnerPanel.setVisible(false);
			oppPartnerPanel.setVisible(false);
			break;
		case TYPE_RENGO:
			partnerPanel.setVisible(true);
			oppPartnerPanel.setVisible(true);
			properCheck.setChecked(false);
			ratedCheck.setChecked(false);
			properChanged();
			break;
		default:
			status.setError("Unknown game type");
			break;
		}
	}
	
	private void updateSettings() {
		ArrayList players = new ArrayList();
		
		int gameType = typeList.getSelectedIndex();
		int color = colorList.getSelectedIndex();
		if(color == 0) {
			color = (Random.nextInt() % 2) + 1;
		}
		
		if(color == 1) {
			//	Black
			players.add(MainPanel.loggedInUser.getUsername());
			players.add(oppText.getUser());
			
			if(gameType == TYPE_RENGO) {
				players.add(partnerText.getUser());
				players.add(oppPartnerText.getUser());
			}
		}
		else {
			//	White
			players.add(oppText.getUser());
			players.add(MainPanel.loggedInUser.getUsername());
			
			if(gameType == TYPE_RENGO) {
				players.add(oppPartnerText.getUser());
				players.add(partnerText.getUser());
			}
		}
		
		invite.setPlayers(players);
		invite.setPlayerAccepted(MainPanel.loggedInUser.getUsername());
		
		invite.setSize(Integer.parseInt(sizeText.getText()));
		invite.setProperHandicap(properCheck.isChecked());
		invite.setHandicap(Integer.parseInt(handicapText.getText()));
		invite.setKomi(Double.parseDouble(komiText.getText()));
		invite.setRated(ratedCheck.isChecked());
		invite.setComment(commentArea.getText());
	}

	private final void loadInvite(boolean newInvite) {
		List players = invite.getPlayerNames();
		int myIndex = players.indexOf(MainPanel.loggedInUser.getUsername());
		
		if(myIndex == -1) {
			status.setError("Couldn't find myself!");
			return;
		}
		
		int oppIndex = (myIndex + 1) % 2;
		int partnerIndex = (myIndex + 2) % 4;
		int oppPartnerIndex = (oppIndex + 2) % 4;
		
		int numPlayers = players.size();
		
		if(numPlayers > 1) {
			oppText.setUser((String)players.get(oppIndex));
		}
		
		if(numPlayers > 2) {
			partnerText.setUser((String)players.get(partnerIndex));
			oppPartnerText.setUser((String)players.get(oppPartnerIndex));
		}
		
		if(numPlayers > 2) {
			typeList.setSelectedIndex(TYPE_RENGO);
		} else {
			typeList.setSelectedIndex(TYPE_NORMAL);
		}
		typeChanged();
		
		if(!newInvite) {
			if(myIndex % 2 == 0) {
				colorList.setSelectedIndex(1);	// Black
			} else {
				colorList.setSelectedIndex(2);	// White
			}
		}
		
		sizeText.setText(String.valueOf(invite.getSize()));
		
		properCheck.setChecked(invite.isProperHandicap());
		handicapText.setText(String.valueOf(invite.getHandicap()));
		komiText.setText(String.valueOf(invite.getKomi()));
		ratedCheck.setChecked(invite.isRated());
		
		String comment = invite.getComment();
		if(comment != null) {
			commentArea.setText(comment);
		}
	}
	
	void handicapChanged() {
		try {
			int handicap = Integer.parseInt(handicapText.getText());
			if(handicap == 0) {
				komiText.setText("6.5");
			} else {
				komiText.setText("0.5");
			}
		}
		catch(NumberFormatException e) {
			komiText.setText("6.5");
		}
	}
	
	private boolean checkValues() {	// NOPMD - Complexity
		int size = 0;
		int handicap = 0;
		
		try {
			size = Integer.parseInt(sizeText.getText());
			handicap = Integer.parseInt(handicapText.getText());
			Double.parseDouble(komiText.getText());
		}
		catch(NumberFormatException e) {
			status.setInfo("Please enter a number");
			return false;
		}
		
		if(oppText.getUser().length() < 1 ||
				(typeList.getSelectedIndex() == TYPE_RENGO &&
						(partnerText.getUser().length() < 1 ||
						oppPartnerText.getUser().length() < 1))) {
			status.setInfo("Enter player names");
			return false;
		}
		
		if(size < 5 || size > 19) {
			status.setError("Illegal board size");
			return false;
		}
		
		if(!ClientGameInfo.isValidHandicap(size, handicap)) {
			status.setInfo("Unsupported handicap");
			return false;
		}
		
		if(!properCheck.isChecked() && handicap != 0 && colorList.getSelectedIndex() == 0) {
			status.setInfo("Can't nigiri in a handicap game");
			return false;
		}
		
		if(size != 19)
		{
			if(ratedCheck.isChecked()) {
				status.setInfo("Only 19x19 games can be rated");
				return false;
			}
			
			if(properCheck.isChecked()) {
				status.setInfo("Proper handicap is only calculated for 19x19 games");
				return false;
			}
		}
		
		if(typeList.getSelectedIndex() == TYPE_RENGO) {
			if(ratedCheck.isChecked()) {
				status.setInfo("Rengo games can't be rated");
				return false;
			}
			
			if(properCheck.isChecked()) {
				status.setInfo("Can't calculate proper handicap for rengo games");
				return false;
			}
		}
		
		status.clear();
		return true;
	}
	
	void sendInvite() {
		if(!checkValues()) {
			return;
		}
		
		updateSettings();

		enableButtons(false);
		MainPanel.msgService.sendInvite(invite, new SendInviteCallback());
	}
	
	private class SendInviteCallback implements AsyncCallback {
		SendInviteCallback() { /* */ }
		
		public void onSuccess(Object result) {
			enableButtons(true);
			
			int err = ((Integer)result).intValue();
			switch(err) {
			case MessageService.STATUS_SUCCESS:
				status.clear();
				hide();
				break;
			case MessageService.STATUS_UNSUPPORTED_HANDICAP:
				status.setError("Unsupported handicap");
				break;
			case MessageService.STATUS_UNSUPPORTED_SIZE:
				status.setError("Unsupported size");
				break;
			case MessageService.STATUS_NO_SUCH_USER:
				status.setError("User does not exist");
				break;
			default:
				status.setError("Unknown error: " + err);
				break;
			}
		}
		
		public void onFailure(Throwable caught) {
			if(caught instanceof NoSessionException) {
				MainPanel.sessionExpired(caught.getMessage());
				return;
			}
			
			enableButtons(true);
			status.setError("Error sending invite:" + caught.getMessage());
		}
	}
	
	void acceptInvite() {
		enableButtons(false);
		
		MainPanel.msgService.acceptInvite(invite.getInviteId(), new AsyncCallback() {
			public void onSuccess(Object result) {
				enableButtons(true);
				
				int err = ((Integer)result).intValue();
				switch(err) {
				case MessageService.STATUS_SUCCESS:
					status.clear();
					hide();
					statusPanel.refresh();
					break;
				case MessageService.STATUS_NO_SUCH_INVITE:
					status.setError("That invite doesn't exist");
					break;
				default:
					status.setError("Unknown error: " + err);
					break;
				}
			}
			
			public void onFailure(Throwable caught) {
				if(caught instanceof NoSessionException) {
					MainPanel.sessionExpired(caught.getMessage());
					return;
				}
				
				enableButtons(true);
				status.setError("Error accepting invite:" + caught.getMessage());
			}
		});
	}
	
	void refuseInvite() {
		enableButtons(false);
		
		MainPanel.msgService.refuseInvite(invite.getInviteId(), new AsyncCallback() {
			public void onSuccess(Object result) {
				enableButtons(true);
				
				int err = ((Integer)result).intValue();
				switch(err) {
				case MessageService.STATUS_SUCCESS:
					status.clear();
					hide();
					statusPanel.refresh();
					break;
				case MessageService.STATUS_NO_SUCH_INVITE:
					status.setError("That invite doesn't exist");
					break;
				default:
					status.setError("Unknown error: " + err);
					break;
				}
			}
			
			public void onFailure(Throwable caught) {
				if(caught instanceof NoSessionException) {
					MainPanel.sessionExpired(caught.getMessage());
					return;
				}
				
				enableButtons(true);
				status.setError("Error refusing invite:" + caught.getMessage());
			}
		});
	}
	
	void enableButtons(boolean enabled) {
		if(sendButton != null) {
			sendButton.setEnabled(enabled);
		}
		
		if(cancelButton != null) {
			cancelButton.setEnabled(enabled);
		}
		
		if(acceptButton != null) {
			acceptButton.setEnabled(enabled);
		}
		
		if(refuseButton != null) {
			refuseButton.setEnabled(enabled);
		}
	}
}
